Skip to content

fix(accounts): preserve closed-account financial history#1730

Open
JSONbored wants to merge 8 commits into
we-promise:mainfrom
JSONbored:codex/docs-closed-account-portability
Open

fix(accounts): preserve closed-account financial history#1730
JSONbored wants to merge 8 commits into
we-promise:mainfrom
JSONbored:codex/docs-closed-account-portability

Conversation

@JSONbored
Copy link
Copy Markdown
Contributor

@JSONbored JSONbored commented May 10, 2026

Summary

What changed

  • Adds a shared historical account scope for active, draft, and disabled accounts while continuing to exclude pending_deletion.
  • Applies that scope to API read surfaces for transactions, balances, valuations, trades, holdings, securities, transfers, and rejected transfers.
  • Adds include_disabled=true support to GET /api/v1/balance_sheet while keeping the default response unchanged.
  • Keeps account write/update flows on existing visible/writable account scopes.
  • Adds missing trades.category_id schema support for the existing trades API category response and request contract.
  • Updates API documentation, rswag specs, generated OpenAPI, and closed-account portability docs.

Why

Closed/disabled accounts are lifecycle state, not deleted history. Hiding them from normal account navigation should not remove their transactions, trades, holdings, valuations, balances, transfer decisions, or securities from API portability and reporting surfaces. pending_deletion remains excluded because it represents destructive cleanup in progress.

Validation

  • bin/rails test test/controllers/api/v1/accounts_controller_test.rb test/controllers/api/v1/transactions_controller_test.rb test/controllers/api/v1/balances_controller_test.rb test/controllers/api/v1/valuations_controller_test.rb test/controllers/api/v1/securities_controller_test.rb test/controllers/api/v1/transfers_controller_test.rb test/controllers/api/v1/rejected_transfers_controller_test.rb test/controllers/api/v1/balance_sheet_controller_test.rb test/controllers/api/v1/trades_controller_test.rb test/controllers/api/v1/holdings_controller_test.rb test/models/balance_sheet_test.rb
  • RAILS_ENV=test bundle exec rake rswag:specs:swaggerize
  • scoped bin/rubocop
  • bin/brakeman --no-pager
  • bin/rails zeitwerk:check
  • git diff --check
  • coderabbit review --agent -t uncommitted -c AGENTS.md
  • Codex Security diff scan: no surviving findings

Notes

  • Draft PR for now.
  • No screenshots included because this is API/model/docs behavior with no user-facing UI layout change.

Summary by CodeRabbit

  • New Features

    • Added include_disabled option to balance sheet and list endpoints to optionally include disabled account data (defaults to false).
  • Improvements

    • List endpoints include disabled accounts while excluding pending-deletion accounts by default.
    • Added UUID validation for account_id/account_ids filters with 422 validation responses.
    • Controller error handling standardized to a generic internal error response.
  • Database

    • Restored nullable trade category reference with foreign key.
  • Documentation

    • Added closed-account portability guide and OpenAPI updates.
  • Tests

    • Expanded integration and request specs for account-state, validation, and error handling.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 10, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b81391ff-c894-4b54-9276-20002f680b94

📥 Commits

Reviewing files that changed from the base of the PR and between 41bb16e and 37ee0a7.

📒 Files selected for processing (10)
  • app/controllers/api/v1/accounts_controller.rb
  • app/controllers/api/v1/holdings_controller.rb
  • app/controllers/api/v1/trades_controller.rb
  • app/controllers/api/v1/transactions_controller.rb
  • app/controllers/api/v1/valuations_controller.rb
  • test/controllers/api/v1/accounts_controller_test.rb
  • test/controllers/api/v1/holdings_controller_test.rb
  • test/controllers/api/v1/trades_controller_test.rb
  • test/controllers/api/v1/transactions_controller_test.rb
  • test/controllers/api/v1/valuations_controller_test.rb
🚧 Files skipped from review as they are similar to previous changes (4)
  • test/controllers/api/v1/accounts_controller_test.rb
  • test/controllers/api/v1/holdings_controller_test.rb
  • test/controllers/api/v1/trades_controller_test.rb
  • app/controllers/api/v1/trades_controller.rb

📝 Walkthrough

Walkthrough

Add historical account scoping and UUID-filter validation: controllers use .historical when including disabled accounts, transaction/holding loaders split readable/writable with UUID validation, RecordNotFound/validation errors are handled, and tests/OpenAPI/docs updated.

Changes

Disabled Account History Support

Layer / File(s) Summary
Account & accessible_account_ids updates
app/controllers/api/v1/accounts_controller.rb, app/controllers/concerns/api/v1/transfer_decision_filtering.rb, app/controllers/concerns/api/v1/security_resource_filtering.rb, app/controllers/api/v1/valuations_controller.rb
Derive accessible account IDs using .historical and branch accounts_scope to scope.historical when include_disabled is requested; include_disabled_accounts? explicitly defaults to false.
Holdings controller: historical scoping & filter validation
app/controllers/api/v1/holdings_controller.rb
Use holding_history_scope for index/show, validate account_id/account_ids UUIDs raising InvalidFilterError, and return 422 validation responses on invalid filters.
Transactions controller: readable/writable loaders & scoping
app/controllers/api/v1/transactions_controller.rb
Split transaction loaders into readable/writable hooks, derive index account scope with .historical, tighten writable account lookups to require writable_by and visible, validate UUID IDs, and rescue RecordNotFound on create.
Transactions controller tests & helpers
test/controllers/api/v1/transactions_controller_test.rb
Add tests for internal error handling, pending_deletion exclusion, disabled-account write blocking, failure-mode stubs, and refactor test helpers (new create_account(status:)).
Valuations tests & helpers
test/controllers/api/v1/valuations_controller_test.rb
Add index test verifying disabled valuations included and pending_deletion excluded; add standardized internal create/update error tests and a helper to create valuation per account status.

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Possibly related PRs

  • Suggested labels: codex, pr:verified

  • Suggested reviewers:

    • jjmata
    • sure-design

"🐇 I hopped through ledgers wide and deep,
disabled rows no longer sleep.
Pending deletions kept at bay,
charts stay steady day by day.
Carrots for the CI — hop, repeat!"

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR introduces database schema changes (trades.category_id) and migration that appears tangentially related to trades but is not mentioned in issue #1443 objectives. Clarify whether the trades.category_id migration is related to the closed-account portability objectives or should be separated into a distinct PR.
Docstring Coverage ⚠️ Warning Docstring coverage is 3.49% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(accounts): preserve closed-account financial history' accurately reflects the main objective: preserving financial history for disabled/closed accounts while maintaining proper visibility controls.
Linked Issues check ✅ Passed The PR directly addresses issue #1443 by implementing persistent historical account data across read endpoints while hiding disabled accounts from default navigation, allowing accurate net-worth calculations for disabled/archived accounts.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@jjmata
Copy link
Copy Markdown
Collaborator

jjmata commented May 12, 2026

Is this really still a draft, @JSONbored?

JSONbored added 2 commits May 14, 2026 01:32
Keep disabled accounts hidden from default account navigation while preserving their historical ledger data for API portability and reporting. Update balance-sheet history, API read scopes, docs, OpenAPI specs, and tests so pending-deletion accounts remain excluded.
@JSONbored JSONbored force-pushed the codex/docs-closed-account-portability branch from a6a9025 to 44e814b Compare May 14, 2026 09:38
@JSONbored JSONbored changed the title docs(api): document closed-account portability fix(accounts): preserve closed-account financial history May 14, 2026
@JSONbored JSONbored marked this pull request as ready for review May 14, 2026 10:12
@superagent-security superagent-security Bot added contributor:verified Contributor passed trust analysis. pr:verified PR passed security analysis. labels May 14, 2026
@JSONbored
Copy link
Copy Markdown
Contributor Author

Is this really still a draft, @JSONbored?

Haha my bad, the plan for this one changed slightly, however working on final pass improvements now. Closes out an existing old issue as well.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 44e814b9b7

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread db/migrate/20260514090000_restore_trade_category_reference.rb Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (12)
app/controllers/api/v1/transactions_controller.rb (5)

195-198: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Error message pattern inconsistency with project standard.

The internal_server_error response uses "An unexpected error occurred" instead of the established pattern "Error: #{e.message}". Based on learnings, API v1 controllers should consistently expose error messages in 500 responses using message: "Error: #{e.message}".

🔧 Proposed fix
     render json: {
       error: "internal_server_error",
-      message: "An unexpected error occurred"
+      message: "Error: #{e.message}"
     }, status: :internal_server_error
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/controllers/api/v1/transactions_controller.rb` around lines 195 - 198,
The 500-response render in TransactionsController uses a generic message; change
the render json block (the hash passed to render json: with keys error and
message) to use the exception message pattern used across API v1 by setting
message to "Error: #{e.message}" (ensure this is inside the rescue block where
`e` is the caught exception, e.g., in the rescue in TransactionsController
action or rescue_from handler so `e` is in scope).

172-175: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Error message pattern inconsistency with project standard.

The internal_server_error response uses "An unexpected error occurred" instead of the established pattern "Error: #{e.message}". Based on learnings, API v1 controllers should consistently expose error messages in 500 responses using message: "Error: #{e.message}".

🔧 Proposed fix
     render json: {
       error: "internal_server_error",
-      message: "An unexpected error occurred"
+      message: "Error: #{e.message}"
     }, status: :internal_server_error
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/controllers/api/v1/transactions_controller.rb` around lines 172 - 175,
The 500 response in the render block inside TransactionsController currently
returns a generic message; update the error response to follow the project
pattern by using the exception's message (i.e., set message to "Error:
#{e.message}") in the render json call that returns status:
:internal_server_error so the controller (transactions_controller.rb) exposes
the actual error via the rescue e variable.

51-54: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Error message pattern inconsistency with project standard.

The internal_server_error response uses "An unexpected error occurred" instead of the established pattern "Error: #{e.message}". Based on learnings, API v1 controllers should consistently expose error messages in 500 responses using message: "Error: #{e.message}".

🔧 Proposed fix
     render json: {
       error: "internal_server_error",
-      message: "An unexpected error occurred"
+      message: "Error: #{e.message}"
     }, status: :internal_server_error
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/controllers/api/v1/transactions_controller.rb` around lines 51 - 54,
Update the 500 error response in API v1 transactions controller to follow the
project pattern by returning the exception message; locate the rescue block in
app/controllers/api/v1/transactions_controller.rb (the block that calls render
json: { error: "internal_server_error", ... }, status: :internal_server_error)
and change the message field to use the rescued exception (message: "Error:
#{e.message}") ensuring the rescue uses `rescue => e` so `e` is available.

126-129: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Error message pattern inconsistency with project standard.

The internal_server_error response uses "An unexpected error occurred" instead of the established pattern "Error: #{e.message}". Based on learnings, API v1 controllers should consistently expose error messages in 500 responses using message: "Error: #{e.message}".

🔧 Proposed fix
     render json: {
       error: "internal_server_error",
-      message: "An unexpected error occurred"
+      message: "Error: #{e.message}"
     }, status: :internal_server_error
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/controllers/api/v1/transactions_controller.rb` around lines 126 - 129,
Replace the static 500 response message with the project's standard error
pattern by using the exception's message: in the TransactionsController rescue
block where you call render json: { error: "internal_server_error", message: ...
}, change the message to "Error: #{e.message}" (keep the same status:
:internal_server_error and the error key), ensuring you reference the exception
variable `e` used in that rescue so the rendered JSON follows the API v1
standard.

65-68: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Error message pattern inconsistency with project standard.

The internal_server_error response uses "An unexpected error occurred" instead of the established pattern "Error: #{e.message}". Based on learnings, API v1 controllers should consistently expose error messages in 500 responses using message: "Error: #{e.message}".

🔧 Proposed fix
     render json: {
       error: "internal_server_error",
-      message: "An unexpected error occurred"
+      message: "Error: #{e.message}"
     }, status: :internal_server_error
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/controllers/api/v1/transactions_controller.rb` around lines 65 - 68, The
500 response in transactions_controller's error handler should follow the
project pattern by exposing the exception message; update the render json block
that currently returns message: "An unexpected error occurred" to use message:
"Error: #{e.message}" (ensure this is inside the rescue that captures the
exception as e) while keeping status: :internal_server_error and the error key
"internal_server_error".
app/controllers/api/v1/accounts_controller.rb (2)

50-53: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Error message pattern inconsistency with project standard.

The internal_server_error response uses "An unexpected error occurred" instead of the established pattern "Error: #{e.message}". Based on learnings, API v1 controllers should consistently expose error messages in 500 responses using message: "Error: #{e.message}".

🔧 Proposed fix
     render json: {
       error: "internal_server_error",
-      message: "An unexpected error occurred"
+      message: "Error: #{e.message}"
     }, status: :internal_server_error
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/controllers/api/v1/accounts_controller.rb` around lines 50 - 53, Change
the 500 response in AccountsController so it follows the project standard by
returning the exception message instead of the static string: replace the
current render json block that sets message: "An unexpected error occurred" with
one that sets message: "Error: #{e.message}" (ensure this is inside the rescue
that has access to the exception variable `e` and update the render call in
AccountsController where the internal_server_error JSON is returned).

23-26: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Error message pattern inconsistency with project standard.

The internal_server_error response uses "An unexpected error occurred" instead of the established pattern "Error: #{e.message}". Based on learnings, API v1 controllers should consistently expose error messages in 500 responses using message: "Error: #{e.message}".

🔧 Proposed fix
     render json: {
       error: "internal_server_error",
-      message: "An unexpected error occurred"
+      message: "Error: #{e.message}"
     }, status: :internal_server_error
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/controllers/api/v1/accounts_controller.rb` around lines 23 - 26, The 500
response in Api::V1::AccountsController currently returns a generic message;
update the render json call in the controller's rescue/exception handling to use
the project standard by setting message: "Error: #{e.message}" (keeping error:
"internal_server_error" and status: :internal_server_error) and ensure the
exception variable e is the one caught in that rescue block.
app/controllers/api/v1/valuations_controller.rb (3)

43-46: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Error message pattern inconsistency with project standard.

The internal_server_error response uses "An unexpected error occurred" instead of the established pattern "Error: #{e.message}". Based on learnings, API v1 controllers should consistently expose error messages in 500 responses using message: "Error: #{e.message}".

🔧 Proposed fix
     render json: {
       error: "internal_server_error",
-      message: "An unexpected error occurred"
+      message: "Error: #{e.message}"
     }, status: :internal_server_error
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/controllers/api/v1/valuations_controller.rb` around lines 43 - 46, The
500 response in ValuationsController currently renders a generic message; update
the rescue block to render the project-standard pattern by returning message:
"Error: #{e.message}" (ensure the rescue uses rescue => e so the exception
variable e is available) while keeping error: "internal_server_error" and
status: :internal_server_error; locate the render call in the controller (e.g.,
inside the rescue in the action or around method handle) and replace the static
message with the interpolated one.

148-151: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Error message pattern inconsistency with project standard.

The internal_server_error response uses "An unexpected error occurred" instead of the established pattern "Error: #{e.message}". Based on learnings, API v1 controllers should consistently expose error messages in 500 responses using message: "Error: #{e.message}".

🔧 Proposed fix
     render json: {
       error: "internal_server_error",
-      message: "An unexpected error occurred"
+      message: "Error: #{e.message}"
     }, status: :internal_server_error
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/controllers/api/v1/valuations_controller.rb` around lines 148 - 151, The
500 response in ValuationsController is returning a hardcoded message instead of
the project-standard error pattern; update the rescue/render block (the code
that currently does render json: { error: "internal_server_error", message: "An
unexpected error occurred" }, status: :internal_server_error) to interpolate the
exception message as message: "Error: #{e.message}" so the response becomes {
error: "internal_server_error", message: "Error: #{e.message}" } while keeping
status: :internal_server_error and using the same rescue variable (e) from the
surrounding rescue handler.

55-58: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Error message pattern inconsistency with project standard.

The internal_server_error response uses "An unexpected error occurred" instead of the established pattern "Error: #{e.message}". Based on learnings, API v1 controllers should consistently expose error messages in 500 responses using message: "Error: #{e.message}".

🔧 Proposed fix
     render json: {
       error: "internal_server_error",
-      message: "An unexpected error occurred"
+      message: "Error: #{e.message}"
     }, status: :internal_server_error
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/controllers/api/v1/valuations_controller.rb` around lines 55 - 58, The
500 response in the ValuationsController is using a generic message; update the
render call in the rescue/exception handling block (where render json: { error:
"internal_server_error", message: ... }, status: :internal_server_error) to
follow project convention by interpolating the exception message (message:
"Error: #{e.message}") so the JSON response exposes the actual error string;
locate the rescue block in ValuationsController (the method handling the request
or the around/rescue_from handler) and replace the static "An unexpected error
occurred" with the interpolated error message.
app/controllers/api/v1/trades_controller.rb (1)

322-326: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Error message pattern inconsistency with project standard.

The internal_server_error response uses "An unexpected error occurred" instead of the established pattern "Error: #{e.message}". Based on learnings, API v1 controllers should consistently expose error messages in 500 responses using message: "Error: #{e.message}".

🔧 Proposed fix
       render json: {
         error: "internal_server_error",
-        message: "An unexpected error occurred"
+        message: "Error: #{e.message}"
       }, status: :internal_server_error
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/controllers/api/v1/trades_controller.rb` around lines 322 - 326, Replace
the hardcoded 500 response message with the project-standard error pattern so
the rescue block in TradesController returns message: "Error: #{e.message}";
locate the rescue/ensure handling around the action methods in
app/controllers/api/v1/trades_controller.rb (the block that currently renders
error: "internal_server_error") and update the JSON response to use the
exception object e to build the message string while keeping status:
:internal_server_error.
app/controllers/api/v1/holdings_controller.rb (1)

123-126: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Error message pattern inconsistency with project standard.

The internal_server_error response uses "An unexpected error occurred" instead of the established pattern "Error: #{e.message}". Based on learnings, API v1 controllers should consistently expose error messages in 500 responses using message: "Error: #{e.message}".

🔧 Proposed fix
       render json: {
         error: "internal_server_error",
-        message: "An unexpected error occurred"
+        message: "Error: #{e.message}"
       }, status: :internal_server_error
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/controllers/api/v1/holdings_controller.rb` around lines 123 - 126, The
500 response in HoldingsController currently returns a generic message; update
the render in the rescue/exception handling block so it follows the project
pattern by using the exception message (e.g., render json: { error:
"internal_server_error", message: "Error: #{e.message}" }, status:
:internal_server_error), ensuring you reference the caught exception variable
(e) in the rescue block and keep the same error key and status.
🧹 Nitpick comments (1)
db/migrate/20260514090000_restore_trade_category_reference.rb (1)

13-13: ⚡ Quick win

Update down to match the on_delete specification.

If the up method is updated to include on_delete: :nullify in the foreign key options, the down method should mirror that specification for consistency, even though remove_reference will drop the constraint regardless.

🔧 Proposed fix
-    remove_reference :trades, :category, foreign_key: true, type: :uuid
+    remove_reference :trades, :category, foreign_key: { on_delete: :nullify }, type: :uuid
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@db/migrate/20260514090000_restore_trade_category_reference.rb` at line 13,
The migration's down method uses remove_reference :trades, :category,
foreign_key: true, type: :uuid but should mirror the up method's foreign key
options; update the remove_reference call to include the same on_delete option
so the foreign key spec matches (e.g., change foreign_key: true to foreign_key:
{ on_delete: :nullify }) for the remove_reference :trades, :category invocation
in the migration.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/controllers/api/v1/balance_sheet_controller.rb`:
- Around line 32-34: The include_disabled_accounts? helper uses
ActiveModel::Type::Boolean#cast which can return nil for missing/blank params;
change it to return a strict boolean (false by default) by coercing the cast
result to a boolean (e.g., using !! or fallback to false) so the API returns
include_disabled: false when the param is absent; update the
include_disabled_accounts? method to use this coercion and ensure
Family#balance_sheet sees a true/false value rather than nil.

In `@db/migrate/20260514090000_restore_trade_category_reference.rb`:
- Line 7: Update the migration line that adds the category reference on trades
so the foreign key uses on_delete: :nullify instead of the default RESTRICT;
specifically change the add_reference call that currently reads add_reference
:trades, :category, null: true, foreign_key: true, type: :uuid to specify the
foreign_key option as a hash with on_delete: :nullify (e.g. foreign_key: {
on_delete: :nullify }) so deleting a Category nullifies trades.category_id to
match transactions.category_id behavior.

In `@docs/api/openapi.yaml`:
- Around line 4413-4414: The 422 response description "invalid date filter" is
too narrow; update the OpenAPI '422' response description entries (the response
object keyed by '422' in the YAML) to a broader phrasing such as "invalid or
malformed account filter" or "invalid filter" so it covers malformed account
filters as well; make the same change for the other occurrence referenced around
lines 6318-6319 by locating the other '422' response objects and replacing their
descriptions with the broader wording.

In `@spec/requests/api/v1/holdings_spec.rb`:
- Around line 107-113: Update the request spec so the account filter parameter
advertises a UUID rather than a generic string: in the spec around the response
'422', 'invalid account filter' (and the parameter definition used by the
holdings spec) change the parameter schema for account_id to require a UUID
(e.g., schema type:string with format:uuid or an appropriate UUID pattern) so
generated OpenAPI contract reflects the new 422 malformed-account-id behavior.

In `@test/controllers/api/v1/balance_sheet_controller_test.rb`:
- Line 35: The test currently only asserts presence of the "include_disabled"
key; update the assertion to also enforce the default value by adding an
equality assertion such as assert_equal false, response_body["include_disabled"]
(or assert_not response_body["include_disabled"]) immediately after the existing
assert response_body.key?("include_disabled") in
test/controllers/api/v1/balance_sheet_controller_test.rb so the default contract
for include_disabled is explicitly verified.

In `@test/controllers/api/v1/holdings_controller_test.rb`:
- Around line 32-85: Add tests covering unauthenticated (401) and
insufficient-scope (403) cases for both index and show: create two tests that
call get api_v1_holdings_url and get api_v1_holding_url(`@holding`) with no
headers (or invalid key) and assert_response :unauthorized and the expected
error payload, and two tests that call the same endpoints using
api_headers(created_api_key_with_no_holdings_scope) (or create_api_key(scopes:
[])) and assert_response :forbidden and the expected error payload; use the
existing helpers api_headers, create_api_key (or similar) and the urls
api_v1_holdings_url and api_v1_holding_url(`@holding`) so the new assertions
mirror the style of the existing tests (JSON.parse(response.body) and checking
"error" field).

In `@test/controllers/api/v1/trades_controller_test.rb`:
- Around line 32-85: Add missing write and auth behavioral tests to the trades
controller spec: create tests that attempt POST/PUT/PATCH/DELETE against
api_v1_trades_url and api_v1_trade_url covering successful create/update/destroy
flows and their error paths (invalid params → assert_response
:unprocessable_entity and invalid date → assert_response :unprocessable_entity),
a test asserting missing auth when no api_headers provided returns :unauthorized
(401), and a test asserting a read-only API key (use the same api_headers helper
but with a read-only key fixture) is blocked from write actions with :forbidden
(403). Reuse existing helpers and fixtures shown in this file (api_headers,
`@api_key`, create_trade, create_investment_account) and mirror the existing
patterns used in "lists trades scoped..." and "does not show a pending deletion
account trade" for parsing responses and asserting error payloads
("validation_failed", "not_found", etc.).

---

Outside diff comments:
In `@app/controllers/api/v1/accounts_controller.rb`:
- Around line 50-53: Change the 500 response in AccountsController so it follows
the project standard by returning the exception message instead of the static
string: replace the current render json block that sets message: "An unexpected
error occurred" with one that sets message: "Error: #{e.message}" (ensure this
is inside the rescue that has access to the exception variable `e` and update
the render call in AccountsController where the internal_server_error JSON is
returned).
- Around line 23-26: The 500 response in Api::V1::AccountsController currently
returns a generic message; update the render json call in the controller's
rescue/exception handling to use the project standard by setting message:
"Error: #{e.message}" (keeping error: "internal_server_error" and status:
:internal_server_error) and ensure the exception variable e is the one caught in
that rescue block.

In `@app/controllers/api/v1/holdings_controller.rb`:
- Around line 123-126: The 500 response in HoldingsController currently returns
a generic message; update the render in the rescue/exception handling block so
it follows the project pattern by using the exception message (e.g., render
json: { error: "internal_server_error", message: "Error: #{e.message}" },
status: :internal_server_error), ensuring you reference the caught exception
variable (e) in the rescue block and keep the same error key and status.

In `@app/controllers/api/v1/trades_controller.rb`:
- Around line 322-326: Replace the hardcoded 500 response message with the
project-standard error pattern so the rescue block in TradesController returns
message: "Error: #{e.message}"; locate the rescue/ensure handling around the
action methods in app/controllers/api/v1/trades_controller.rb (the block that
currently renders error: "internal_server_error") and update the JSON response
to use the exception object e to build the message string while keeping status:
:internal_server_error.

In `@app/controllers/api/v1/transactions_controller.rb`:
- Around line 195-198: The 500-response render in TransactionsController uses a
generic message; change the render json block (the hash passed to render json:
with keys error and message) to use the exception message pattern used across
API v1 by setting message to "Error: #{e.message}" (ensure this is inside the
rescue block where `e` is the caught exception, e.g., in the rescue in
TransactionsController action or rescue_from handler so `e` is in scope).
- Around line 172-175: The 500 response in the render block inside
TransactionsController currently returns a generic message; update the error
response to follow the project pattern by using the exception's message (i.e.,
set message to "Error: #{e.message}") in the render json call that returns
status: :internal_server_error so the controller (transactions_controller.rb)
exposes the actual error via the rescue e variable.
- Around line 51-54: Update the 500 error response in API v1 transactions
controller to follow the project pattern by returning the exception message;
locate the rescue block in app/controllers/api/v1/transactions_controller.rb
(the block that calls render json: { error: "internal_server_error", ... },
status: :internal_server_error) and change the message field to use the rescued
exception (message: "Error: #{e.message}") ensuring the rescue uses `rescue =>
e` so `e` is available.
- Around line 126-129: Replace the static 500 response message with the
project's standard error pattern by using the exception's message: in the
TransactionsController rescue block where you call render json: { error:
"internal_server_error", message: ... }, change the message to "Error:
#{e.message}" (keep the same status: :internal_server_error and the error key),
ensuring you reference the exception variable `e` used in that rescue so the
rendered JSON follows the API v1 standard.
- Around line 65-68: The 500 response in transactions_controller's error handler
should follow the project pattern by exposing the exception message; update the
render json block that currently returns message: "An unexpected error occurred"
to use message: "Error: #{e.message}" (ensure this is inside the rescue that
captures the exception as e) while keeping status: :internal_server_error and
the error key "internal_server_error".

In `@app/controllers/api/v1/valuations_controller.rb`:
- Around line 43-46: The 500 response in ValuationsController currently renders
a generic message; update the rescue block to render the project-standard
pattern by returning message: "Error: #{e.message}" (ensure the rescue uses
rescue => e so the exception variable e is available) while keeping error:
"internal_server_error" and status: :internal_server_error; locate the render
call in the controller (e.g., inside the rescue in the action or around method
handle) and replace the static message with the interpolated one.
- Around line 148-151: The 500 response in ValuationsController is returning a
hardcoded message instead of the project-standard error pattern; update the
rescue/render block (the code that currently does render json: { error:
"internal_server_error", message: "An unexpected error occurred" }, status:
:internal_server_error) to interpolate the exception message as message: "Error:
#{e.message}" so the response becomes { error: "internal_server_error", message:
"Error: #{e.message}" } while keeping status: :internal_server_error and using
the same rescue variable (e) from the surrounding rescue handler.
- Around line 55-58: The 500 response in the ValuationsController is using a
generic message; update the render call in the rescue/exception handling block
(where render json: { error: "internal_server_error", message: ... }, status:
:internal_server_error) to follow project convention by interpolating the
exception message (message: "Error: #{e.message}") so the JSON response exposes
the actual error string; locate the rescue block in ValuationsController (the
method handling the request or the around/rescue_from handler) and replace the
static "An unexpected error occurred" with the interpolated error message.

---

Nitpick comments:
In `@db/migrate/20260514090000_restore_trade_category_reference.rb`:
- Line 13: The migration's down method uses remove_reference :trades, :category,
foreign_key: true, type: :uuid but should mirror the up method's foreign key
options; update the remove_reference call to include the same on_delete option
so the foreign key spec matches (e.g., change foreign_key: true to foreign_key:
{ on_delete: :nullify }) for the remove_reference :trades, :category invocation
in the migration.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fc629522-3d1e-4dc8-9b21-bc16b9599c49

📥 Commits

Reviewing files that changed from the base of the PR and between 81e6687 and 44e814b.

📒 Files selected for processing (39)
  • app/controllers/api/v1/accounts_controller.rb
  • app/controllers/api/v1/balance_sheet_controller.rb
  • app/controllers/api/v1/balances_controller.rb
  • app/controllers/api/v1/holdings_controller.rb
  • app/controllers/api/v1/trades_controller.rb
  • app/controllers/api/v1/transactions_controller.rb
  • app/controllers/api/v1/valuations_controller.rb
  • app/controllers/concerns/api/v1/security_resource_filtering.rb
  • app/controllers/concerns/api/v1/transfer_decision_filtering.rb
  • app/models/account.rb
  • app/models/balance_sheet.rb
  • app/models/balance_sheet/account_totals.rb
  • app/models/balance_sheet/net_worth_series_builder.rb
  • app/models/family.rb
  • db/migrate/20260514090000_restore_trade_category_reference.rb
  • db/schema.rb
  • docs/api/closed-account-portability.md
  • docs/api/openapi.yaml
  • spec/requests/api/v1/accounts_spec.rb
  • spec/requests/api/v1/balance_sheet_spec.rb
  • spec/requests/api/v1/balances_spec.rb
  • spec/requests/api/v1/holdings_spec.rb
  • spec/requests/api/v1/rejected_transfers_spec.rb
  • spec/requests/api/v1/securities_spec.rb
  • spec/requests/api/v1/trades_spec.rb
  • spec/requests/api/v1/transfers_spec.rb
  • spec/requests/api/v1/valuations_spec.rb
  • spec/swagger_helper.rb
  • test/controllers/api/v1/accounts_controller_test.rb
  • test/controllers/api/v1/balance_sheet_controller_test.rb
  • test/controllers/api/v1/balances_controller_test.rb
  • test/controllers/api/v1/holdings_controller_test.rb
  • test/controllers/api/v1/rejected_transfers_controller_test.rb
  • test/controllers/api/v1/securities_controller_test.rb
  • test/controllers/api/v1/trades_controller_test.rb
  • test/controllers/api/v1/transactions_controller_test.rb
  • test/controllers/api/v1/transfers_controller_test.rb
  • test/controllers/api/v1/valuations_controller_test.rb
  • test/models/balance_sheet_test.rb

Comment thread app/controllers/api/v1/balance_sheet_controller.rb
Comment thread db/migrate/20260514090000_restore_trade_category_reference.rb Outdated
Comment thread docs/api/openapi.yaml Outdated
Comment thread spec/requests/api/v1/holdings_spec.rb
Comment thread test/controllers/api/v1/balance_sheet_controller_test.rb
Comment thread test/controllers/api/v1/holdings_controller_test.rb
Comment thread test/controllers/api/v1/trades_controller_test.rb
@superagent-security superagent-security Bot removed the pr:verified PR passed security analysis. label May 14, 2026
@JSONbored JSONbored marked this pull request as draft May 14, 2026 12:00
@JSONbored JSONbored marked this pull request as ready for review May 14, 2026 12:06
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
app/controllers/api/v1/balance_sheet_controller.rb (1)

10-34: 💤 Low value

Optional: memoize include_disabled_accounts? to avoid double-casting.

The helper is invoked twice per request (once for family.balance_sheet and once for the JSON response). Memoizing keeps both call sites consistent and avoids re-casting params.

♻️ Suggested refactor
   def show
     family = current_resource_owner.family
+    include_disabled = include_disabled_accounts?
     balance_sheet = family.balance_sheet(
       user: current_resource_owner,
-      include_disabled: include_disabled_accounts?
+      include_disabled: include_disabled
     )

     render json: {
       currency: family.currency,
-      include_disabled: include_disabled_accounts?,
+      include_disabled: include_disabled,
       net_worth: balance_sheet.net_worth_money.as_json,
       assets: balance_sheet.assets.total_money.as_json,
       liabilities: balance_sheet.liabilities.total_money.as_json
     }
   end
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/controllers/api/v1/balance_sheet_controller.rb` around lines 10 - 34,
Memoize include_disabled_accounts? so the param is cast only once: replace the
current method implementation with an instance-variable-backed memo (e.g. set
`@include_disabled_accounts` to the result of
ActiveModel::Type::Boolean.new.cast(params[:include_disabled]) || false) and
update usages (family.balance_sheet(..., include_disabled:
include_disabled_accounts?) and the JSON render) to keep both call sites
consistent and avoid double-casting; keep the method name
include_disabled_accounts? and preserve returning a boolean.
app/controllers/api/v1/transactions_controller.rb (1)

209-241: 💤 Low value

Optional: collapse the readable/writable transaction lookups into one helper.

The two methods share UUID validation, family/transaction lookup, entry assignment, and rescue handling — only the merged account scope differs. A small parameterization removes the duplication and keeps the two flows aligned.

♻️ Suggested refactor
-    def set_readable_transaction
-      raise ActiveRecord::RecordNotFound unless valid_uuid?(params[:id])
-
-      family = current_resource_owner.family
-      `@transaction` = family.transactions
-        .joins(entry: :account)
-        .merge(Account.accessible_by(current_resource_owner))
-        .merge(Account.historical)
-        .find(params[:id])
-      `@entry` = `@transaction.entry`
-    rescue ActiveRecord::RecordNotFound
-      render json: {
-        error: "not_found",
-        message: "Transaction not found"
-      }, status: :not_found
-    end
-
-    def set_writable_transaction
-      raise ActiveRecord::RecordNotFound unless valid_uuid?(params[:id])
-
-      family = current_resource_owner.family
-      `@transaction` = family.transactions
-        .joins(entry: :account)
-        .merge(Account.writable_by(current_resource_owner))
-        .merge(Account.visible)
-        .find(params[:id])
-      `@entry` = `@transaction.entry`
-    rescue ActiveRecord::RecordNotFound
-      render json: {
-        error: "not_found",
-        message: "Transaction not found"
-      }, status: :not_found
-    end
+    def set_readable_transaction
+      load_transaction!(Account.accessible_by(current_resource_owner), Account.historical)
+    end
+
+    def set_writable_transaction
+      load_transaction!(Account.writable_by(current_resource_owner), Account.visible)
+    end
+
+    def load_transaction!(*account_scopes)
+      raise ActiveRecord::RecordNotFound unless valid_uuid?(params[:id])
+
+      query = current_resource_owner.family.transactions.joins(entry: :account)
+      `@transaction` = account_scopes.reduce(query) { |q, scope| q.merge(scope) }.find(params[:id])
+      `@entry` = `@transaction.entry`
+    rescue ActiveRecord::RecordNotFound
+      render json: {
+        error: "not_found",
+        message: "Transaction not found"
+      }, status: :not_found
+    end
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/controllers/api/v1/transactions_controller.rb` around lines 209 - 241,
The two methods set_readable_transaction and set_writable_transaction are
duplicated; extract a single helper (e.g., set_transaction_with_account_scope or
set_transaction(scope)) that performs the UUID check (valid_uuid?(params[:id])),
loads family = current_resource_owner.family, performs
family.transactions.joins(entry:
:account).merge(<account_scope>).find(params[:id]), assigns `@transaction` and
`@entry`, and rescues ActiveRecord::RecordNotFound to render the same JSON; then
replace set_readable_transaction to call the helper with
Account.accessible_by(current_resource_owner).merge(Account.historical) and
set_writable_transaction to call it with
Account.writable_by(current_resource_owner).merge(Account.visible).
app/controllers/api/v1/trades_controller.rb (1)

119-140: 💤 Low value

Optional: deduplicate set_readable_trade and set_writable_trade.

The two helpers diverge only in the merged account scopes; the UUID guard, family lookup, entry assignment, and rescue branch are identical. Consider parameterizing the scopes through a shared loader to reduce maintenance surface (same pattern observation applies to transactions_controller.rb).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/controllers/api/v1/trades_controller.rb` around lines 119 - 140, Both
set_readable_trade and set_writable_trade duplicate UUID validation, family
lookup, entry assignment, and rescue behavior; extract a single loader that
accepts an account_scope parameter and use it from both methods. Create a
private method (e.g., load_trade_with_account_scope(account_scope)) that
performs valid_uuid?(params[:id]), finds the trade via either
trade_history_scope.find or family.trades.joins(entry:
:account).merge(account_scope).find, sets `@trade` and `@entry`, and rescues
ActiveRecord::RecordNotFound to render the same JSON error; then refactor
set_readable_trade and set_writable_trade to call this loader with appropriate
scopes (for writable use
Account.writable_by(current_resource_owner).merge(Account.visible), for readable
pass Account.visible or nil). Apply the same pattern to
transactions_controller.rb where similar duplication exists.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@app/controllers/api/v1/balance_sheet_controller.rb`:
- Around line 10-34: Memoize include_disabled_accounts? so the param is cast
only once: replace the current method implementation with an
instance-variable-backed memo (e.g. set `@include_disabled_accounts` to the result
of ActiveModel::Type::Boolean.new.cast(params[:include_disabled]) || false) and
update usages (family.balance_sheet(..., include_disabled:
include_disabled_accounts?) and the JSON render) to keep both call sites
consistent and avoid double-casting; keep the method name
include_disabled_accounts? and preserve returning a boolean.

In `@app/controllers/api/v1/trades_controller.rb`:
- Around line 119-140: Both set_readable_trade and set_writable_trade duplicate
UUID validation, family lookup, entry assignment, and rescue behavior; extract a
single loader that accepts an account_scope parameter and use it from both
methods. Create a private method (e.g.,
load_trade_with_account_scope(account_scope)) that performs
valid_uuid?(params[:id]), finds the trade via either trade_history_scope.find or
family.trades.joins(entry: :account).merge(account_scope).find, sets `@trade` and
`@entry`, and rescues ActiveRecord::RecordNotFound to render the same JSON error;
then refactor set_readable_trade and set_writable_trade to call this loader with
appropriate scopes (for writable use
Account.writable_by(current_resource_owner).merge(Account.visible), for readable
pass Account.visible or nil). Apply the same pattern to
transactions_controller.rb where similar duplication exists.

In `@app/controllers/api/v1/transactions_controller.rb`:
- Around line 209-241: The two methods set_readable_transaction and
set_writable_transaction are duplicated; extract a single helper (e.g.,
set_transaction_with_account_scope or set_transaction(scope)) that performs the
UUID check (valid_uuid?(params[:id])), loads family =
current_resource_owner.family, performs family.transactions.joins(entry:
:account).merge(<account_scope>).find(params[:id]), assigns `@transaction` and
`@entry`, and rescues ActiveRecord::RecordNotFound to render the same JSON; then
replace set_readable_transaction to call the helper with
Account.accessible_by(current_resource_owner).merge(Account.historical) and
set_writable_transaction to call it with
Account.writable_by(current_resource_owner).merge(Account.visible).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0f6176d7-2086-49b6-a206-1458141f6c60

📥 Commits

Reviewing files that changed from the base of the PR and between 44e814b and db75c7d.

📒 Files selected for processing (18)
  • app/controllers/api/v1/accounts_controller.rb
  • app/controllers/api/v1/balance_sheet_controller.rb
  • app/controllers/api/v1/holdings_controller.rb
  • app/controllers/api/v1/trades_controller.rb
  • app/controllers/api/v1/transactions_controller.rb
  • app/controllers/api/v1/valuations_controller.rb
  • db/migrate/20260514090000_restore_trade_category_reference.rb
  • db/schema.rb
  • docs/api/openapi.yaml
  • spec/requests/api/v1/holdings_spec.rb
  • spec/requests/api/v1/trades_spec.rb
  • test/controllers/api/v1/accounts_controller_test.rb
  • test/controllers/api/v1/balance_sheet_controller_test.rb
  • test/controllers/api/v1/holdings_controller_test.rb
  • test/controllers/api/v1/trades_controller_test.rb
  • test/controllers/api/v1/transactions_controller_test.rb
  • test/controllers/api/v1/valuations_controller_test.rb
  • test/models/trade_test.rb
✅ Files skipped from review due to trivial changes (1)
  • db/schema.rb
🚧 Files skipped from review as they are similar to previous changes (5)
  • test/controllers/api/v1/balance_sheet_controller_test.rb
  • db/migrate/20260514090000_restore_trade_category_reference.rb
  • app/controllers/api/v1/holdings_controller.rb
  • spec/requests/api/v1/holdings_spec.rb
  • spec/requests/api/v1/trades_spec.rb

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
test/controllers/api/v1/holdings_controller_test.rb (1)

32-45: ⚡ Quick win

Consider adding explicit coverage for active account holdings.

The test focuses on disabled and pending_deletion accounts, which is great for verifying the new historical scoping behavior. However, since @account is created as active but immediately disabled (line 33), the test never explicitly verifies that active account holdings are included in the index.

For completeness, consider either:

  • Testing both active and disabled holdings in this test, or
  • Adding a separate baseline test that verifies active account holdings are accessible
♻️ Proposed enhancement
 test "lists holdings scoped to accessible historical accounts" do
+  active_holding = `@holding`  # Keep reference to the active account holding
+  disabled_account = create_investment_account(status: "active", name: "Disabled Holding")
+  disabled_holding = create_holding(disabled_account, ticker: "DIS#{SecureRandom.hex(4).upcase}")
+  disabled_account.disable!
-  `@account.disable`!
   pending_deletion_account = create_investment_account(status: "pending_deletion", name: "Pending Delete Holding")
   pending_deletion_holding = create_holding(pending_deletion_account, ticker: "PDH#{SecureRandom.hex(4).upcase}")

   get api_v1_holdings_url, headers: api_headers(`@api_key`)

   assert_response :success
   response_data = JSON.parse(response.body)
   holding_ids = response_data["holdings"].map { |holding| holding["id"] }
+  assert_includes holding_ids, active_holding.id
-  assert_includes holding_ids, `@holding.id`
+  assert_includes holding_ids, disabled_holding.id
   assert_not_includes holding_ids, pending_deletion_holding.id
   assert_not_includes holding_ids, `@other_holding.id`
 end
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/controllers/api/v1/holdings_controller_test.rb` around lines 32 - 45,
The current test "lists holdings scoped to accessible historical accounts"
disables `@account`, so it never asserts that holdings for an active account are
returned; update the test to explicitly cover an active account by either not
disabling `@account` (leave it active) and asserting `@holding.id` is included, or
create a separate active_account and active_holding via
create_investment_account and create_holding and assert their id appears in the
response from get api_v1_holdings_url (use api_headers(`@api_key`)); keep the
existing assertions for pending_deletion_holding and `@other_holding` unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@test/controllers/api/v1/holdings_controller_test.rb`:
- Around line 32-45: The current test "lists holdings scoped to accessible
historical accounts" disables `@account`, so it never asserts that holdings for an
active account are returned; update the test to explicitly cover an active
account by either not disabling `@account` (leave it active) and asserting
`@holding.id` is included, or create a separate active_account and active_holding
via create_investment_account and create_holding and assert their id appears in
the response from get api_v1_holdings_url (use api_headers(`@api_key`)); keep the
existing assertions for pending_deletion_holding and `@other_holding` unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4eb792e1-3ca8-40f9-9648-d16660686602

📥 Commits

Reviewing files that changed from the base of the PR and between db75c7d and 31a2b23.

📒 Files selected for processing (4)
  • app/controllers/api/v1/balance_sheet_controller.rb
  • app/controllers/api/v1/trades_controller.rb
  • app/controllers/api/v1/transactions_controller.rb
  • test/controllers/api/v1/holdings_controller_test.rb
🚧 Files skipped from review as they are similar to previous changes (3)
  • app/controllers/api/v1/balance_sheet_controller.rb
  • app/controllers/api/v1/transactions_controller.rb
  • app/controllers/api/v1/trades_controller.rb

Copy link
Copy Markdown

This is otherwise very clean and ready to merge. One issue to fix before landing: the API error rescue block exposes raw exception messages ("Error: #{e.message}") to API consumers. This leaks internal implementation details and diverges from the existing "An unexpected error occurred" pattern used elsewhere. Please revert those rescue strings to the opaque message.


Generated by Claude Code

@JSONbored
Copy link
Copy Markdown
Contributor Author

This is otherwise very clean and ready to merge. One issue to fix before landing: the API error rescue block exposes raw exception messages ("Error: #{e.message}") to API consumers. This leaks internal implementation details and diverges from the existing "An unexpected error occurred" pattern used elsewhere. Please revert those rescue strings to the opaque message.

Generated by Claude Code

This has been resolved as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor:verified Contributor passed trust analysis.

Development

Successfully merging this pull request may close these issues.

Improvement: Net worth reporting incorrect net worth changes when "archived" accounts - Request for comments

3 participants